/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics.PackedVector;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

namespace Cobalt.Content.Pipeline.Quake.Shaders
{
	internal class ShaderOptimizer
	{
		public void OptimizeShader( Shader shader )
		{
			for( int s = 0; s < shader.Stages.Count; s++ )
			{
				ShaderStage stage = shader.Stages[s];

				SimplifyStage( stage );
					
				ShaderStage colapsed = TryCollapse( shader.Stages[s - 1], shader.Stages[s] );
				if( colapsed != null )
				{
					shader.Stages[s] = colapsed;
					shader.Stages.RemoveAt( --s );
				}
			}
		}

		private void SimplifyStage( ShaderStage stage )
		{
			if( stage.AlphaBlend.Enable )
			{
				if( stage.AlphaBlend.SourceBlend == Blend.One &&
					stage.AlphaBlend.DestBlend == Blend.Zero )
				{
					stage.AlphaBlend.Enable = false;
				}
			}
		}

		private ShaderStage TryCollapse( ShaderStage first, ShaderStage second )
		{
			if( IsSolid( second.AlphaTest ) && IsSolid( second.AlphaBlend ) )
			{
				//second has no alpha holes

				if( !second.DepthTest.Enable )
					//second will overwrite first regardless of z
					return second;

				//it overwrites the first, unless there is depth buffer trickery
				if( !first.DepthTest.Enable || !first.DepthTest.WriteEnable )
					//first has not laid down any z, second *will* overwrite it
					return second;

				switch( second.DepthTest.Function )
				{
				case CompareFunction.LessEqual:
				case CompareFunction.Equal:
				case CompareFunction.GreaterEqual:
				case CompareFunction.Greater:
				case CompareFunction.Always:
					//second will always pass depth test
					return second;
				}
			}

			if( first.AlphaBlend.Enable )
				//can't collapse when we're already blending
				return null;


			
			return null;
		}

		public static bool IsSolid( ShaderStage stage )
		{
			return IsSolid( stage.AlphaTest ) &&
				IsSolid( stage.AlphaBlend );
		}
		
		public static bool IsSolid( AlphaTest atest )
		{
			if( !atest.Enable )
				return true;
			
			switch( atest.Function )
			{
			case CompareFunction.Always:
				return true;

			case CompareFunction.GreaterEqual:
				if( atest.Reference == 0 )
					return true;
				break;

			case CompareFunction.LessEqual:
				if( atest.Reference == 0xFF )
					return true;
				break;
			}

			return false;
		}

		public static bool IsSolid( AlphaBlend blend )
		{
			if( !blend.Enable )
				return true;

			if( blend.DestBlend == Blend.Zero )
				return true;

			return false;
		}

		private bool UsesVertexColor( ShaderStage stage )
		{
			for( int b = 0; b < stage.Bundles.Count; b++ )
			{
				if( UsesVertexColor( stage.Bundles[b].TextureBlend, b == 0 ) )
					return true;
			}

			return false;
		}

		private bool UsesVertexColor( TextureBlend blend, bool isFirst )
		{
			if( UsesVertexColor( blend.Source0, isFirst ) )
				return true;

			switch( blend.ColorOperation )
			{
			case TextureBlendOp.Replace:
				//only uses Source0
				break;

			case TextureBlendOp.Add:
			case TextureBlendOp.Modulate:
			case TextureBlendOp.Subtract:
				if( UsesVertexColor( blend.Source1, isFirst ) )
					return true;
				break;

			case TextureBlendOp.Interpolate:
				if( UsesVertexColor( blend.Source1, isFirst ) ||
					UsesVertexColor( blend.Source2, isFirst ) )
					return true;
				break;
			}

			return false;
		}

		private bool UsesVertexColor( TextureBlendSource source, bool isFirst )
		{
			switch( source.ColorSource )
			{
			case TextureBlendSourceSrc.Previous:
				if( isFirst )
					goto case TextureBlendSourceSrc.Primary;
				break;

			case TextureBlendSourceSrc.Primary:
				if( source.ColorOperation == TextureBlendSourceColorOp.Color ||
					source.ColorOperation == TextureBlendSourceColorOp.InverseColor )
					return true;
				break;
			}

			return false;
		}

		private bool UsesVertexAlpha( ShaderStage stage )
		{
			if( !IsSolid( stage.AlphaTest ) )
			{
				//alpha test doesn't care about it

				if( !stage.AlphaBlend.Enable )
					return false;

				bool blendUsesAlpha = false;
				switch( stage.AlphaBlend.SourceBlend )
				{
				case Blend.SourceAlpha:
				case Blend.SourceAlphaSaturation:
				case Blend.InverseSourceAlpha:
				case Blend.BothInverseSourceAlpha:
					blendUsesAlpha = true;
					break;
				}

				switch( stage.AlphaBlend.DestBlend )
				{
				case Blend.SourceAlpha:
				case Blend.SourceAlphaSaturation:
				case Blend.InverseSourceAlpha:
				case Blend.BothInverseSourceAlpha:
					blendUsesAlpha = true;
					break;
				}

				if( !blendUsesAlpha )
					return false;
			}

			for( int b = 0; b < stage.Bundles.Count; b++ )
			{
				if( UsesVertexAlpha( stage.Bundles[b].TextureBlend, b == 0 ) )
					return true;
			}

			return false;
		}

		private bool UsesVertexAlpha( TextureBlend blend, bool isFirst )
		{
			if( UsesVertexAlphaCl( blend.Source0, isFirst ) ||
				UsesVertexAlphaA( blend.Source0, isFirst ) )
				return true;

			switch( blend.ColorOperation )
			{
			case TextureBlendOp.Replace:
				//only uses Source0
				break;

			case TextureBlendOp.Add:
			case TextureBlendOp.Modulate:
			case TextureBlendOp.Subtract:
				if( UsesVertexAlphaCl( blend.Source1, isFirst ) )
					return true;
				break;

			case TextureBlendOp.Interpolate:
				if( UsesVertexAlphaCl( blend.Source1, isFirst ) ||
					UsesVertexAlphaCl( blend.Source2, isFirst ) )
					return true;
				break;
			}

			switch( blend.AlphaOperation )
			{
			case TextureBlendOp.Replace:
				//only uses Source0
				break;

			case TextureBlendOp.Add:
			case TextureBlendOp.Modulate:
			case TextureBlendOp.Subtract:
				if( UsesVertexAlphaA( blend.Source1, isFirst ) )
					return true;
				break;

			case TextureBlendOp.Interpolate:
				if( UsesVertexAlphaA( blend.Source1, isFirst ) ||
					UsesVertexAlphaA( blend.Source2, isFirst ) )
					return true;
				break;
			}

			return false;
		}

		private bool UsesVertexAlphaCl( TextureBlendSource source, bool isFirst )
		{
			switch( source.ColorSource )
			{
			case TextureBlendSourceSrc.Previous:
				if( isFirst )
					goto case TextureBlendSourceSrc.Primary;
				break;

			case TextureBlendSourceSrc.Primary:
				if( source.ColorOperation == TextureBlendSourceColorOp.Alpha ||
					source.ColorOperation == TextureBlendSourceColorOp.InverseAlpha )
					return true;
				break;
			}

			return false;
		}

		private bool UsesVertexAlphaA( TextureBlendSource source, bool isFirst )
		{
			switch( source.AlphaSource )
			{
			case TextureBlendSourceSrc.Previous:
				if( isFirst )
					goto case TextureBlendSourceSrc.Primary;
				break;

			case TextureBlendSourceSrc.Primary:
				return true;
			}

			return false;
		}

		private float? CollapseConstantWave( Waveform wave )
		{
			if( wave.Function == WaveFunction.None )
				return 0;

			if( wave.Amplitude == 0 )
				return wave.Base;

			if( wave.Frequency == 0 )
			{
				float t = wave.Phase;
				t -= (int)t;

				float v;
				switch( wave.Function )
				{
				case WaveFunction.Sine:
					v = (float)Math.Sin( t * MathHelper.TwoPi );
					break;

				case WaveFunction.Square:
					v = t < 0.5F ? 1 : -1;
					break;

				case WaveFunction.Triangle:
					if( t < 0.25F )
						v = t * 4;
					else if( t < 0.75F )
						v = 1 - (t - 0.25F) * 4;
					else
						v = (t - 0.75F) * 4 - 1;
					break;

				case WaveFunction.Sawtooth:
					v = t;
					break;

				case WaveFunction.InverseSawtooth:
					v = 1 - t;
					break;

				case WaveFunction.Noise:
					v = t; //ToDo :(
					break;

				default:
					return null;
				}
			}

			return null;
		}
	}
}